/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.nViron.Render;

import com.sun.opengl.util.GLUT;
import com.sun.opengl.util.j2d.TextRenderer;
import com.sun.opengl.util.texture.Texture;
import com.sun.opengl.util.texture.TextureIO;
import java.awt.Font;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.glu.GLU;
import javax.media.opengl.glu.GLUquadric;
import org.nViron.client.Config;
import org.nViron.nvCommon.nvMercator;
import org.nViron.nvCommon.nvPosition;
import org.nViron.nvRender.nvFrustum;
import org.nViron.nvRender.nvRenderer;
import org.nViron.nvRender.nvVec3;
import org.nViron.nvSim.nvSimulator;


/**
 *
 * @author Roland
 */
public class RendererGL extends nvRenderer {

    private TextRenderer textrenderer;
    private GLAutoDrawable drawable;
    private GLU glu;
    private GLUT glut;
    private Texture backgroundTex;
    private GLUquadric gluQuadric;
    private long timestamp;
    private int framecount;
    private float fps;

    public RendererGL(GLAutoDrawable _drawable, nvSimulator _sim) {
        super(_sim);
        drawable = _drawable;
        glu = new GLU();
        glut = new GLUT();
        gluQuadric = glu.gluNewQuadric();
        timestamp = System.currentTimeMillis();
    }

    public GLAutoDrawable getDrawable() {
        return drawable;
    }

    public void setDrawable(GLAutoDrawable drawable) {
        this.drawable = drawable;
    }

    public FrustumGL getFrustumGL() {
        return (FrustumGL)frustum;
    }

    public GLU getGlu() {
        return glu;
    }

    public GLUquadric getGluQuadric() {
        return gluQuadric;
    }

    public GLUT getGlut() {
        return glut;
    }

    public TextRenderer getTextrenderer() {
        return textrenderer;
    }

    @Override
    public void init() {
        super.init();
        textrenderer = new TextRenderer(new Font("Calibri", Font.PLAIN, 12));
        textrenderer.setSmoothing(true);
        Config config = (Config) simulator.getConfig();
        File texFile = new File(config.getTexturePath() + "background1024.png");
        try {
            backgroundTex = TextureIO.newTexture(texFile, false);
        } catch (IOException ex) {
            Logger.getLogger(RendererGL.class.getName()).log(Level.SEVERE, null, ex);
        }

        GL gl = drawable.getGL();
        gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA);
        gl.glEnable(gl.GL_COLOR_MATERIAL);
        gl.glShadeModel(gl.GL_SMOOTH);
        gl.glEnable(gl.GL_CULL_FACE);
        gl.glEnable(gl.GL_DEPTH_TEST);
        gl.glEnable(gl.GL_BLEND);
    }

    @Override
    protected void setupCamera() {
        nvMercator mercator = (nvMercator)simulator.getProjection();
        mercator.setCameraPos(simulator.getCameraPosition());

        GL gl = drawable.getGL();
        gl.glMatrixMode(gl.GL_MODELVIEW);
        gl.glLoadIdentity();

        nvVec3 p = mercator.geoToViz(simulator.getCameraPosition());
        nvVec3 l = mercator.geoToViz(simulator.getLookAtPosition());
        nvVec3 u = new nvVec3(0, 1, 0);
        glu.gluLookAt(p.x, p.y, p.z, l.x, l.y, l.z, u.x, u.y, u.z);
        frustum.setCamera(p, l, u);
    }

    @Override
    protected void renderForeground() {
        framecount++;
        long diff = System.currentTimeMillis() - timestamp;
        if (diff > 1000)
        {
            timestamp += diff;
            fps = 1000.0f * (float)framecount / (float)diff;
            framecount = 0;
        }
        textrenderer.beginRendering(drawable.getWidth(), drawable.getHeight());
        textrenderer.setColor(1.0f, 1.0f, 1.0f, 1.0f);
        textrenderer.draw("FPS "+fps, 5, 5);
        textrenderer.endRendering();
    }

    @Override
    protected void renderBackground() {
        GL gl = drawable.getGL();

        gl.glMatrixMode(gl.GL_PROJECTION);
        gl.glPushMatrix();
        gl.glLoadIdentity();
        gl.glMatrixMode(gl.GL_MODELVIEW);
        gl.glPushMatrix();
        gl.glLoadIdentity();

        gl.glDisable(gl.GL_DEPTH_TEST);
        gl.glDepthMask(false);

        backgroundTex.bind();
        backgroundTex.enable();
        gl.glEnable(gl.GL_BLEND);
        gl.glBegin(gl.GL_QUADS);
        gl.glColor4f(1, 1, 1, 0.15f);
        gl.glTexCoord2f(0, 0);
        gl.glVertex2f(-1, -1);
        gl.glTexCoord2f(1, 0);
        gl.glVertex2f(1, -1);
        gl.glTexCoord2f(1, 1);
        gl.glVertex2f(1, 1);
        gl.glTexCoord2f(0, 1);
        gl.glVertex2f(-1, 1);
        gl.glEnd();
        backgroundTex.disable();

        gl.glDepthMask(true);
        gl.glEnable(gl.GL_DEPTH_TEST);

        gl.glMatrixMode(gl.GL_PROJECTION);
        gl.glPopMatrix();
        gl.glMatrixMode(gl.GL_MODELVIEW);
        gl.glPopMatrix();
    }

    @Override
    protected void renderMap() {
        enableFog();
        disableLighting();

        if (simulator.getMapSource() != null && simulator.getProjection() != null && frustum.doesIntersectGround()) {

            nvVec3 ulVec = new nvVec3(Math.min(frustum.gnl.x, Math.min(frustum.gnr.x, Math.min(frustum.gfr.x, frustum.gfl.x))),
                                    0.0f,
                                    Math.min(frustum.gnl.z, Math.min(frustum.gnr.z, Math.min(frustum.gfr.z, frustum.gfl.z))));
            nvVec3 lrVec = new nvVec3(Math.max(frustum.gnl.x, Math.max(frustum.gnr.x, Math.max(frustum.gfr.x, frustum.gfl.x))),
                                    0.0f,
                                    Math.max(frustum.gnl.z, Math.max(frustum.gnr.z, Math.max(frustum.gfr.z, frustum.gfl.z))));

            nvPosition ul = simulator.getProjection().vizToGeo(ulVec);
            nvPosition lr = simulator.getProjection().vizToGeo(lrVec);

            simulator.getMapSource().setAreaOfInterest(ul, lr);
            simulator.getMapSource().render();
        }
    }

    @Override
    protected void renderGrid() {
        GL gl = drawable.getGL();

        if (!frustum.doesIntersectGround()) {
            return;
        }

        disableFog();
        disableLighting();
        gl.glDisable(gl.GL_DEPTH_TEST);

        // render frustum ground intersection
        gl.glColor4f(0.75f, 0.75f, 0.0f, 0.75f);
        gl.glBegin(gl.GL_LINES);
            gl.glVertex3f(frustum.gnl.x, 0, frustum.gnl.z);
            gl.glVertex3f(frustum.gnr.x, 0, frustum.gnr.z);
            gl.glVertex3f(frustum.gfr.x, 0, frustum.gfr.z);
            gl.glVertex3f(frustum.gfl.x, 0, frustum.gfl.z);
        gl.glEnd();

        // render lines on frustum ground intersection
        gl.glColor4f(0.75f, 0.75f, 0.75f, 0.25f);
        gl.glBegin(gl.GL_LINES);

        nvVec3 min = new nvVec3(Math.min(frustum.gnl.x, Math.min(frustum.gnr.x, Math.min(frustum.gfr.x, frustum.gfl.x))),
                                0.0f,
                                Math.min(frustum.gnl.z, Math.min(frustum.gnr.z, Math.min(frustum.gfr.z, frustum.gfl.z))));
        nvVec3 max = new nvVec3(Math.max(frustum.gnl.x, Math.max(frustum.gnr.x, Math.max(frustum.gfr.x, frustum.gfl.x))),
                                0.0f,
                                Math.max(frustum.gnl.z, Math.max(frustum.gnr.z, Math.max(frustum.gfr.z, frustum.gfl.z))));

        float steps = 25.0f;
        float deltaX = (max.x - min.x) / steps;
        for (int x = 0; x <=steps; x++) {
            gl.glVertex3f(min.x + deltaX * x, 0, min.z);
            gl.glVertex3f(min.x + deltaX * x, 0, max.z);
        }

        float deltaZ = (max.z - min.z) / steps;
        for (int z = 0; z < steps; z++) {
            gl.glVertex3f(min.x, 0, min.z + deltaZ * z);
            gl.glVertex3f(max.x, 0, min.z + deltaZ * z);
        }
        gl.glEnd();

        disableFog();
    }

    @Override
    public void onReshape(int x, int y, int width, int height) {
        float aspect = (float) width / (float) height;
        frustum.setProjection(frustum.getFOV(), aspect, frustum.getNearP(), frustum.getFarP());
        glu.gluPerspective(frustum.getFOV(), frustum.getAspect(), frustum.getNearP(), frustum.getFarP());
    }

    @Override
    protected void onPreRenderScene() {
        super.onPreRenderScene();
    }

    @Override
    protected void onPostRenderScene() {
        super.onPostRenderScene();
        GL gl = drawable.getGL();
        gl.glEnable(gl.GL_BLEND);
        gl.glDisable(gl.GL_DEPTH_TEST);
        gl.glColor4f(1, 1, 0, 1.0f);

        enableFog();
        enableLighting();

        //       int viewport[] = new int[4];
        //       double mvmatrix[] = new double[16];
        //       double projmatrix[] = new double[16];
        //       double winPos[] = new double[3];

        if (frustum.doesIntersectGround())
        {
            float radius = simulator.getProjection().geoToVizApprox(10.0f);
            float num = 10.0f;

            nvVec3 min = new nvVec3(Math.min(frustum.gnl.x, Math.min(frustum.gnr.x, Math.min(frustum.gfr.x, frustum.gfl.x))),
                                    0.0f,
                                    Math.min(frustum.gnl.z, Math.min(frustum.gnr.z, Math.min(frustum.gfr.z, frustum.gfl.z))));
            nvVec3 max = new nvVec3(Math.max(frustum.gnl.x, Math.max(frustum.gnr.x, Math.max(frustum.gfr.x, frustum.gfl.x))),
                                    0.0f,
                                    Math.max(frustum.gnl.z, Math.max(frustum.gnr.z, Math.max(frustum.gfr.z, frustum.gfl.z))));

            float deltaX = (max.x - min.x) / num;
            float deltaZ = (max.z - min.z) / num;
            for (float x = min.x; x <= max.x; x += deltaX) {
                for (float z = min.z; z <= max.z; z += deltaZ) {

                    if (frustum.sphereInFrustum(new nvVec3(x, radius, z), radius) != nvFrustum.ALIGN.OUTSIDE)
                    {
                        gl.glPushMatrix();
                        gl.glTranslatef(x, radius, z);
                        glu.gluSphere(gluQuadric, radius, 15, 10);
                        gl.glPopMatrix();
                    }
                }
            }
        }
        disableLighting();
        disableFog();

        gl.glColor4f(1, 1, 1, 0.5f);
        // frustum.drawLines();
    }

    private void enableFog() {
        GL gl = drawable.getGL();
        float density = 5.0f;
        float fogColor[] = {0.1f, 0.1f, 0.1f, 1.0f};
        gl.glEnable(gl.GL_FOG);
        gl.glFogi(gl.GL_FOG_MODE, gl.GL_LINEAR);
        gl.glFogfv(gl.GL_FOG_COLOR, fogColor, 0);
        gl.glFogf(gl.GL_FOG_DENSITY, density);
        gl.glFogf(gl.GL_FOG_START, frustum.getFarP()/2.0f);
        gl.glFogf(gl.GL_FOG_END, frustum.getFarP());
        gl.glHint(gl.GL_FOG_HINT, gl.GL_NICEST);
    }

    private void disableFog() {
        GL gl = drawable.getGL();
        gl.glDisable(gl.GL_FOG);
    }

    private void disableLighting() {
        GL gl = drawable.getGL();
        gl.glDisable(gl.GL_LIGHTING);
        gl.glDisable(gl.GL_LIGHT0);
    }

    private void enableLighting() {
        GL gl = drawable.getGL();
        gl.glEnable(gl.GL_LIGHTING);
        nvVec3 pos = simulator.getProjection().geoToViz(simulator.getLookAtPosition());
        float lightPos[] = {pos.x, pos.y, pos.z, 0};
        float ambient[] = {0.3f, 0.3f, 0.3f, 1.0f};
        float diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
        gl.glLightfv(gl.GL_LIGHT0, gl.GL_POSITION, lightPos, 0);
        gl.glLightfv(gl.GL_LIGHT0, gl.GL_AMBIENT, ambient, 0);
        gl.glLightfv(gl.GL_LIGHT0, gl.GL_DIFFUSE, diffuse, 0);
        gl.glEnable(gl.GL_LIGHT0);
    }

    @Override
    protected nvFrustum createFrustum() {
        return new FrustumGL(drawable);
    }
}
